﻿using System;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Collections;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace Microsoft.Office.OneNote.XmlViewer.Controls.Views
{
    public partial class ViewObject : ViewBase
    {
        private object _root;

        public ViewObject()
        {
            InitializeComponent();
        }

        public override void Navigate(OneNoteHierarchyObject obj)
        {
            RootObject = obj;
        }

        public override void Navigate(OneNoteHierarchy hierarchy)
        {
            RootObject = hierarchy;
        }

        public override void LeaveCurrentObject()
        {
            RootObject = null;
        }

        public object RootObject
        {
            get { return _root; }
            set
            {
                if (_root != value)
                {
                    tree.BeginUpdate();
                    tree.Nodes.Clear();

                    _root = value;

                    if (_root != null)
                    {
                        tree.Nodes.Add(CreateRootNode(_root));
                    }

                    tree.EndUpdate();
                }
            }
        }

        public static TreeNode CreateRootNode(object obj)
        {
            return new RootNode(obj);
        }

        public static TreeNode[] CreateNodesFor(object obj)
        {
            ICollection collection = (obj as ICollection);
            
            if (collection != null)
            {
                return (from itm in collection.OfType<Object>()
                        select CreateRootNode(itm)).ToArray();
            }

            return (from member in obj.GetType().GetMembers(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance)
                    where member is PropertyInfo || member is FieldInfo
                    let node = CreateNodeFor(obj, member)
                    where node != null
                    select node).ToArray();
        }

        public static TreeNode CreateNodeFor(object obj, MemberInfo member)
        {
            PropertyInfo pi = (member as PropertyInfo);

            if (pi != null)
            {
                ParameterInfo[] parameters = pi.GetIndexParameters();

                if (parameters != null && parameters.Length > 0)
                {
                    return null;
                }
            }

            if (pi != null || member is FieldInfo)
            {
                Exception error;
                Object value = GetValue(obj, member, out error);

                return new MemberNode(member.Name, value, error);
            }

            return null;
        }

        public static object GetValue(object obj, MemberInfo member, out Exception error)
        {
            PropertyInfo property = member as PropertyInfo;
            if (property != null)
            {
                try
                {
                    error = null;
                    return property.GetValue(obj, null);
                }
                catch (TargetInvocationException exc)
                {
                    error = exc.GetBaseException();
                    return null;
                }
            }

            FieldInfo field = member as FieldInfo;

            if (field != null)
            {
                error = null;
                return field.GetValue(obj);
            }

            error = null;
            return null;
        }

        private sealed class MemberNode : LazyTreeNode
        {
            private readonly string _memberName;
            private readonly object _value;

            public MemberNode(string memberName, object value, Exception error)
            {
                _memberName = memberName;
                _value = error ?? value;

                if (error == null)
                {
                    Text = memberName + " = " + ObjectToString(value);

                    if (IsAtomic(value))
                    {
                        Nodes.Clear();
                        IsLoaded = true;
                    }
                }
                else
                {
                    Text = "Exception retrieving property " + memberName + " = " + ObjectToString(error);
                }
            }

            public override void OnBeforeFirstExpand()
            {
                Nodes.AddRange(CreateNodesFor(_value));
            }

            private static bool IsAtomic(object value)
            {
                if (value == null || value is string || value is DateTime)
                {
                    return true;
                }

                Type type = value.GetType();

                return type.IsPrimitive || type.IsEnum;
            }
        }

        private sealed class RootNode : LazyTreeNode
        {
            private readonly object _obj;

            public RootNode(object obj)
            {
                _obj = obj;
                Text = ObjectToString(obj);
                if (obj == null)
                {
                    IsLoaded = true;
                }
            }

            public override void OnBeforeFirstExpand()
            {
                Nodes.AddRange(CreateNodesFor(_obj));
            }
        }

        private static string ObjectToString(object value)
        {
            if (value == null)
            {
                return "(null)";
            }

            DebuggerDisplayAttribute dbdispl = value.GetType()
                .GetCustomAttributes(typeof(DebuggerDisplayAttribute), true)
                .OfType<DebuggerDisplayAttribute>().FirstOrDefault();

            if (dbdispl != null)
            {
                return PropertyRefRegex.Replace(dbdispl.Value, m => GetValue(value, m.Groups[1].Value));
            }

            return value.ToString();
        }

        private static string GetValue(object obj, string memberName)
        {
            MemberInfo member = obj.GetType().GetMember(memberName,
                MemberTypes.Field | MemberTypes.Property,
                BindingFlags.Public | BindingFlags.Instance).FirstOrDefault();

            FieldInfo field = (member as FieldInfo);
            if (field != null)
            {
                return ObjectToString(field.GetValue(obj));
            }

            PropertyInfo property = (member as PropertyInfo);
            if (property != null)
            {
                return ObjectToString(property.GetValue(obj, null));
            }

            return string.Empty;
        }

        private static Regex PropertyRefRegex = new Regex("\\{([^\\}]+)\\}", RegexOptions.Compiled);
    }
}